From 8bcb031418a2676a2f64080ae74c51cd9e9fbf30 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 21 Sep 2020 21:05:04 +0200 Subject: [PATCH] gtk-demo: Add GskGLShaderNode demo Add adds a demo showing off GskGLShaderNode in various ways. It has a transistion widget, using some examples from gl-transitions.com, with child widgets being both images, a GL area and real widgets (that let you edit the transition shaders themselves. It also has a fancy fire effect on hove on the buttons. --- demos/gtk-demo/cogs2.glsl | 226 ++++++++++++++++++ demos/gtk-demo/demo.gresource.xml | 13 + demos/gtk-demo/fire.glsl | 72 ++++++ demos/gtk-demo/gltransition.c | 329 ++++++++++++++++++++++++++ demos/gtk-demo/gskshaderpaintable.c | 337 ++++++++++++++++++++++++++ demos/gtk-demo/gskshaderpaintable.h | 53 +++++ demos/gtk-demo/gtkshaderbin.c | 241 +++++++++++++++++++ demos/gtk-demo/gtkshaderbin.h | 22 ++ demos/gtk-demo/gtkshaderstack.c | 352 ++++++++++++++++++++++++++++ demos/gtk-demo/gtkshaderstack.h | 21 ++ demos/gtk-demo/meson.build | 4 + demos/gtk-demo/transition1.glsl | 33 +++ demos/gtk-demo/transition2.glsl | 34 +++ demos/gtk-demo/transition3.glsl | 27 +++ demos/gtk-demo/transition4.glsl | 41 ++++ 15 files changed, 1805 insertions(+) create mode 100644 demos/gtk-demo/cogs2.glsl create mode 100644 demos/gtk-demo/fire.glsl create mode 100644 demos/gtk-demo/gltransition.c create mode 100644 demos/gtk-demo/gskshaderpaintable.c create mode 100644 demos/gtk-demo/gskshaderpaintable.h create mode 100644 demos/gtk-demo/gtkshaderbin.c create mode 100644 demos/gtk-demo/gtkshaderbin.h create mode 100644 demos/gtk-demo/gtkshaderstack.c create mode 100644 demos/gtk-demo/gtkshaderstack.h create mode 100644 demos/gtk-demo/transition1.glsl create mode 100644 demos/gtk-demo/transition2.glsl create mode 100644 demos/gtk-demo/transition3.glsl create mode 100644 demos/gtk-demo/transition4.glsl diff --git a/demos/gtk-demo/cogs2.glsl b/demos/gtk-demo/cogs2.glsl new file mode 100644 index 0000000000..8d131eca8d --- /dev/null +++ b/demos/gtk-demo/cogs2.glsl @@ -0,0 +1,226 @@ +uniform float iTime; + +// Originally from: https://www.shadertoy.com/view/3ljyDD +// License CC0: Hexagonal tiling + cog wheels +// Nothing fancy, just hexagonal tiling + cog wheels + +#define PI 3.141592654 +#define TAU (2.0*PI) +#define MROT(a) mat2(cos(a), sin(a), -sin(a), cos(a)) + +float hash(in vec2 co) { + return fract(sin(dot(co.xy ,vec2(12.9898,58.233))) * 13758.5453); +} + +float pcos(float a) { + return 0.5 + 0.5*cos(a); +} + +void rot(inout vec2 p, float a) { + float c = cos(a); + float s = sin(a); + p = vec2(c*p.x + s*p.y, -s*p.x + c*p.y); +} + +float modPolar(inout vec2 p, float repetitions) { + float angle = 2.0*PI/repetitions; + float a = atan(p.y, p.x) + angle/2.; + float r = length(p); + float c = floor(a/angle); + a = mod(a,angle) - angle/2.; + p = vec2(cos(a), sin(a))*r; + // For an odd number of repetitions, fix cell index of the cell in -x direction + // (cell index would be e.g. -5 and 5 in the two halves of the cell): + if (abs(c) >= (repetitions/2.0)) c = abs(c); + return c; +} + +float pmin(float a, float b, float k) { + float h = clamp( 0.5+0.5*(b-a)/k, 0.0, 1.0 ); + return mix( b, a, h ) - k*h*(1.0-h); +} + +const vec2 sz = vec2(1.0, sqrt(3.0)); +const vec2 hsz = 0.5*sz; +const float smallCount = 16.0; + +vec2 hextile(inout vec2 p) { + // See Art of Code: Hexagonal Tiling Explained! + // https://www.youtube.com/watch?v=VmrIDyYiJBA + + vec2 p1 = mod(p, sz)-hsz; + vec2 p2 = mod(p - hsz*1.0, sz)-hsz; + vec2 p3 = mix(p2, p1, vec2(length(p1) < length(p2))); + vec2 n = p3 - p; + p = p3; + + return n; +} + +float circle(vec2 p, float r) { + return length(p) - r; +} + +float box(vec2 p, vec2 b) { + vec2 d = abs(p)-b; + return length(max(d,0.0)) + min(max(d.x,d.y),0.0); +} + +float unevenCapsule(vec2 p, float r1, float r2, float h) { + p.x = abs(p.x); + float b = (r1-r2)/h; + float a = sqrt(1.0-b*b); + float k = dot(p,vec2(-b,a)); + if( k < 0.0 ) return length(p) - r1; + if( k > a*h ) return length(p-vec2(0.0,h)) - r2; + return dot(p, vec2(a,b) ) - r1; +} + +float cogwheel(vec2 p, float innerRadius, float outerRadius, float cogs, float holes) { + float cogWidth = 0.25*innerRadius*TAU/cogs; + + float d0 = circle(p, innerRadius); + + vec2 icp = p; + modPolar(icp, holes); + icp -= vec2(innerRadius*0.55, 0.0); + float d1 = circle(icp, innerRadius*0.25); + + vec2 cp = p; + modPolar(cp, cogs); + cp -= vec2(innerRadius, 0.0); + float d2 = unevenCapsule(cp.yx, cogWidth, cogWidth*0.75, (outerRadius-innerRadius)); + + float d3 = circle(p, innerRadius*0.20); + + float d = 1E6; + d = min(d, d0); + d = pmin(d, d2, 0.5*cogWidth); + d = min(d, d2); + d = max(d, -d1); + d = max(d, -d3); + + return d; +} + +float ccell1(vec2 p, float r) { + float d = 1E6; + const float bigCount = 60.0; + + vec2 cp0 = p; + rot(cp0, -iTime*TAU/bigCount); + float d0 = cogwheel(cp0, 0.36, 0.38, bigCount, 5.0); + + vec2 cp1 = p; + float nm = modPolar(cp1, 6.0); + + cp1 -= vec2(0.5, 0.0); + rot(cp1, 0.2+TAU*nm/2.0 + iTime*TAU/smallCount); + float d1 = cogwheel(cp1, 0.11, 0.125, smallCount, 5.0); + + d = min(d, d0); + d = min(d, d1); + return d; +} + +float ccell2(vec2 p, float r) { + float d = 1E6; + vec2 cp0 = p; + float nm = modPolar(cp0, 6.0); + vec2 cp1 = cp0; + const float off = 0.275; + const float count = smallCount + 2.0; + cp0 -= vec2(off, 0.0); + rot(cp0, 0.+TAU*nm/2.0 - iTime*TAU/count); + float d0 = cogwheel(cp0, 0.09, 0.105, count, 5.0); + + + cp1 -= vec2(0.5, 0.0); + rot(cp1, 0.2+TAU*nm/2.0 + iTime*TAU/smallCount); + float d1 = cogwheel(cp1, 0.11, 0.125, smallCount, 5.0); + + float l = length(p); + float d2 = l - (off+0.055); + float d3 = d2 + 0.020;; + + vec2 tp0 = p; + modPolar(tp0, 60.0); + tp0.x -= off; + float d4 = box(tp0, vec2(0.0125, 0.005)); + + float ctime = -(iTime*0.05 + r)*TAU; + + vec2 tp1 = p; + rot(tp1, ctime*12.0); + tp1.x -= 0.13; + float d5 = box(tp1, vec2(0.125, 0.005)); + + vec2 tp2 = p; + rot(tp2, ctime); + tp2.x -= 0.13*0.5; + float d6 = box(tp2, vec2(0.125*0.5, 0.0075)); + + float d7 = l - 0.025; + float d8 = l - 0.0125; + + d = min(d, d0); + d = min(d, d1); + d = min(d, d2); + d = max(d, -d3); + d = min(d, d4); + d = min(d, d5); + d = min(d, d6); + d = min(d, d7); + d = max(d, -d8); + + return d; +} + +float df(vec2 p, float scale, inout vec2 nn) { + p /= scale; + nn = hextile(p); + nn = round(nn); + float r = hash(nn); + + float d;; + + if (r < 0.5) { + d = ccell1(p, r); + } else { + d = ccell2(p, r); + } + + return d*scale; +} + +vec3 postProcess(vec3 col, vec2 q) { + //col = saturate(col); + col=pow(clamp(col,0.0,1.0),vec3(0.75)); + col=col*0.6+0.4*col*col*(3.0-2.0*col); // contrast + col=mix(col, vec3(dot(col, vec3(0.33))), -0.4); // satuation + col*=0.5+0.5*pow(19.0*q.x*q.y*(1.0-q.x)*(1.0-q.y),0.7); // vigneting + return col; +} + +void mainImage(out vec4 fragColor, in vec2 fragCoord, in vec2 resolution, in vec2 uv) { + vec2 q = fragCoord/resolution.xy; + vec2 p = -1.0 + 2.0*q; + p.x *= resolution.x/resolution.y; + float tm = iTime*0.1; + p += vec2(cos(tm), sin(tm*sqrt(0.5))); + float z = mix(0.5, 1.0, pcos(tm*sqrt(0.3))); + float aa = 4.0 / resolution.y; + + vec2 nn = vec2(0.0); + float d = df(p, z, nn); + + vec3 col = vec3(160.0)/vec3(255.0); + vec3 baseCol = vec3(0.3); + vec4 logoCol = vec4(baseCol, 1.0)*smoothstep(-aa, 0.0, -d); + col = mix(col, logoCol.xyz, pow(logoCol.w, 8.0)); + col += 0.4*pow(abs(sin(20.0*d)), 0.6); + + col = postProcess(col, q); + + fragColor = vec4(col, 1.0); +} diff --git a/demos/gtk-demo/demo.gresource.xml b/demos/gtk-demo/demo.gresource.xml index 39d6ad2db8..cc9727481c 100644 --- a/demos/gtk-demo/demo.gresource.xml +++ b/demos/gtk-demo/demo.gresource.xml @@ -133,6 +133,18 @@ cogs.glsl glowingstars.glsl + + gtkshaderstack.c + gtkshaderstack.h + gtkshaderbin.h + gtkshaderbin.c + fire.glsl + transition1.glsl + transition2.glsl + transition3.glsl + transition4.glsl + cogs2.glsl + iconscroll.ui @@ -247,6 +259,7 @@ gears.c gestures.c glarea.c + gltransition.c headerbar.c hypertext.c iconscroll.c diff --git a/demos/gtk-demo/fire.glsl b/demos/gtk-demo/fire.glsl new file mode 100644 index 0000000000..e62fe1bd39 --- /dev/null +++ b/demos/gtk-demo/fire.glsl @@ -0,0 +1,72 @@ +uniform float u_time; +uniform sampler2D u_texture1; + +/* 2D -> [0..1] random number generator */ +float random(vec2 st) { + return fract(sin(dot(st.xy, + vec2(12.9898,78.233))) * + 43758.5453123); +} + +/* Generate a smoothed 2d noise based on random() */ +float noise(vec2 v) { + /* Round point v to integer grid grid */ + vec2 grid_point = floor(v); + /* Randomize in grid corners */ + float corner1 = random(grid_point); + float corner2 = random(grid_point + vec2(1, 0)); + float corner3 = random(grid_point + vec2(0, 1)); + float corner4 = random(grid_point + vec2(1, 1)); + /* Interpolate smoothly between grid points */ + vec2 fraction = smoothstep(vec2(0.0), vec2(1.0), fract(v)); + return mix(mix(corner1, corner2, fraction.x), + mix(corner3, corner4, fraction.x), + fraction.y); +} + +/* fractal brownian motion noice, see https://www.iquilezles.org/www/articles/fbm/fbm.htm */ +float fbm(in vec2 x) +{ + const float octaveScale = 1.9; + const float G = 0.5; + float f = 1.0; + float a = 1.0; + float t = 0.0; + int numOctaves = 5; + for (int i = 0; i < numOctaves; i++) { + t += a*noise(f*x); + f *= octaveScale; + a *= G; + } + + return t; +} + +void mainImage(out vec4 fragColor, in vec2 fragCoord, in vec2 resolution, in vec2 uv) +{ + vec2 xy = fragCoord / resolution; + + float zoom = 3.0 - sin(u_time*0.5)*0.3; + + // Normalize coord to height of widget + vec2 p = (vec2 (-resolution.x/2.0 + fragCoord.x, resolution.y - fragCoord.y) / resolution.yy)* zoom; + + // Use recursive incantations of fbm + float q1 = fbm(p - vec2(0.8, 0.3) * u_time); + float q2 = fbm(p - vec2(0.5, 1.3) * u_time); + float r = fbm(2.0*p + vec2(q1,q2) - vec2(0.0, 1.0)*u_time*10.0 *0.4); + + // Compute intensity, mostly on the bottom + float w = 2.0 * r * p.y; + + // Smooth out left/right side and fade in at start + w /= smoothstep(0.0,0.1, xy.x)* smoothstep(0.0,0.1, 1.0-xy.x) * smoothstep(0.0,0.4, u_time); + + // Compute colors + vec3 c = vec3(1.0,.2,.05); + vec3 color = 1.0 / (w*w/c + 1.0); + + // Mix in widget + vec4 widget = GskTexture(u_texture1,uv); + fragColor = gsk_premultiply(mix(vec4(color,1), widget, 1.0-color.x)); +} diff --git a/demos/gtk-demo/gltransition.c b/demos/gtk-demo/gltransition.c new file mode 100644 index 0000000000..24b6f96f30 --- /dev/null +++ b/demos/gtk-demo/gltransition.c @@ -0,0 +1,329 @@ +/* OpenGL/Transitions + * #Keywords: OpenGL, shader + * + * Create transitions between pages using a custom fragment shader. + * + * The example transitions here are taken from gl-transitions.com, and you + * can edit the shader code itself on the last page of the stack. + * + * The transitions work with arbitrary content. We use images, shaders + * GL areas and plain old widgets to demonstrate this. + * + * The demo also shows some sample fire effects on the buttons. + */ + +#include +#include +#include "gtkshaderstack.h" +#include "gtkshaderbin.h" +#include "gtkshadertoy.h" +#include "gskshaderpaintable.h" + +static GtkWidget *demo_window = NULL; + +static void +close_window (GtkWidget *widget) +{ + /* Reset the state */ + demo_window = NULL; +} + +static void +text_changed (GtkTextBuffer *buffer, + GtkWidget *button) +{ + gtk_widget_show (button); +} + +static void +apply_text (GtkWidget *button, + GtkTextBuffer *buffer) +{ + GtkWidget *stack; + GskGLShader *shader; + GtkTextIter start, end; + char *text; + + stack = g_object_get_data (G_OBJECT (button), "the-stack"); + + gtk_text_buffer_get_bounds (buffer, &start, &end); + text = gtk_text_buffer_get_text (buffer, &start, &end, TRUE); + + GBytes *bytes = g_bytes_new_take (text, strlen (text)); + shader = gsk_gl_shader_new_from_bytes (bytes); + + gtk_shader_stack_set_shader (GTK_SHADER_STACK (stack), shader); + + g_object_unref (shader); + g_bytes_unref (bytes); + + gtk_widget_hide (button); +} + +static void +go_back (GtkWidget *button, + GtkWidget *stack) +{ + gtk_shader_stack_transition (GTK_SHADER_STACK (stack), FALSE); +} + +static void +go_forward (GtkWidget *button, + GtkWidget *stack) +{ + gtk_shader_stack_transition (GTK_SHADER_STACK (stack), TRUE); +} + +static void +clicked_cb (GtkGestureClick *gesture, + guint n_pressed, + double x, + double y, + gpointer data) +{ + gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED); +} + +static GtkWidget * +fire_bin_new (void) +{ + GtkWidget *bin = gtk_shader_bin_new (); + static GskGLShader *shader = NULL; + + if (shader == NULL) + shader = gsk_gl_shader_new_from_resource ("/gltransition/fire.glsl"); + + gtk_shader_bin_add_shader (GTK_SHADER_BIN (bin), shader, GTK_STATE_FLAG_PRELIGHT, GTK_STATE_FLAG_PRELIGHT); + + return bin; +} + +static GtkWidget * +new_shadertoy (const char *path) +{ + GBytes *shader; + GtkWidget *toy; + + toy = gtk_shadertoy_new (); + shader = g_resources_lookup_data (path, 0, NULL); + gtk_shadertoy_set_image_shader (GTK_SHADERTOY (toy), + g_bytes_get_data (shader, NULL)); + g_bytes_unref (shader); + + return toy; +} + +static gboolean +update_paintable (GtkWidget *widget, + GdkFrameClock *frame_clock, + gpointer user_data) +{ + GskShaderPaintable *paintable; + gint64 frame_time; + + paintable = GSK_SHADER_PAINTABLE (gtk_picture_get_paintable (GTK_PICTURE (widget))); + frame_time = gdk_frame_clock_get_frame_time (frame_clock); + gsk_shader_paintable_update_time (paintable, 0, frame_time); + + return G_SOURCE_CONTINUE; +} + +static GtkWidget * +make_shader_stack (const char *name, + const char *resource_path, + GtkWidget *scale) +{ + GtkWidget *stack, *child, *widget, *vbox, *hbox, *bin; + GtkWidget *label, *button, *tv; + GskGLShader *shader; + GObjectClass *class; + GParamSpecFloat *pspec; + GtkAdjustment *adjustment; + GtkTextBuffer *buffer; + GBytes *bytes; + GtkEventController *controller; + GtkCssProvider *provider; + GdkPaintable *paintable; + + stack = gtk_shader_stack_new (); + shader = gsk_gl_shader_new_from_resource (resource_path); + gtk_shader_stack_set_shader (GTK_SHADER_STACK (stack), shader); + g_object_unref (shader); + + child = gtk_picture_new_for_resource ("/css_pixbufs/background.jpg"); + gtk_picture_set_can_shrink (GTK_PICTURE (child), TRUE); + gtk_shader_stack_add_child (GTK_SHADER_STACK (stack), child); + + shader = gsk_gl_shader_new_from_resource ("/gltransition/cogs2.glsl"); + paintable = gsk_shader_paintable_new (shader, NULL); + + child = gtk_picture_new_for_paintable (paintable); + gtk_widget_add_tick_callback (child, update_paintable, NULL, NULL); + gtk_picture_set_can_shrink (GTK_PICTURE (child), TRUE); + gtk_shader_stack_add_child (GTK_SHADER_STACK (stack), child); + + child = gtk_picture_new_for_resource ("/transparent/portland-rose.jpg"); + gtk_picture_set_can_shrink (GTK_PICTURE (child), TRUE); + gtk_shader_stack_add_child (GTK_SHADER_STACK (stack), child); + + child = new_shadertoy ("/shadertoy/neon.glsl"); + gtk_shader_stack_add_child (GTK_SHADER_STACK (stack), child); + + child = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + + class = g_type_class_ref (GTK_TYPE_SHADER_STACK); + pspec = G_PARAM_SPEC_FLOAT (g_object_class_find_property (class, "duration")); + + adjustment = gtk_range_get_adjustment (GTK_RANGE (scale)); + if (gtk_adjustment_get_lower (adjustment) == 0.0 && + gtk_adjustment_get_upper (adjustment) == 0.0) + { + gtk_adjustment_configure (adjustment, + pspec->default_value, + pspec->minimum, + pspec->maximum, + 0.1, 0.5, 0); + } + + g_type_class_unref (class); + + g_object_bind_property (adjustment, "value", + stack, "duration", + G_BINDING_DEFAULT); + + widget = gtk_scrolled_window_new (); + gtk_scrolled_window_set_has_frame (GTK_SCROLLED_WINDOW (widget), TRUE); + gtk_widget_set_hexpand (widget, TRUE); + gtk_widget_set_vexpand (widget, TRUE); + + controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ()); + gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), 0); + g_signal_connect (controller, "released", G_CALLBACK (clicked_cb), NULL); + gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_BUBBLE); + gtk_widget_add_controller (GTK_WIDGET (widget), controller); + + tv = gtk_text_view_new (); + gtk_text_view_set_left_margin (GTK_TEXT_VIEW (tv), 4); + gtk_text_view_set_right_margin (GTK_TEXT_VIEW (tv), 4); + gtk_text_view_set_top_margin (GTK_TEXT_VIEW (tv), 4); + gtk_text_view_set_bottom_margin (GTK_TEXT_VIEW (tv), 4); + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); + bytes = g_resources_lookup_data (resource_path, 0, NULL); + gtk_text_buffer_set_text (buffer, + (const char *)g_bytes_get_data (bytes, NULL), + g_bytes_get_size (bytes)); + g_bytes_unref (bytes); + gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (widget), tv); + + gtk_box_append (GTK_BOX (child), widget); + + gtk_shader_stack_add_child (GTK_SHADER_STACK (stack), child); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + + widget = gtk_center_box_new (); + label = gtk_label_new (name); + gtk_widget_add_css_class (label, "title-4"); + gtk_widget_set_size_request (label, -1, 26); + gtk_center_box_set_center_widget (GTK_CENTER_BOX (widget), label); + + button = gtk_button_new_from_icon_name ("view-refresh-symbolic"); + g_signal_connect (buffer, "changed", G_CALLBACK (text_changed), button); + g_object_set_data (G_OBJECT (button), "the-stack", stack); + g_signal_connect (button, "clicked", G_CALLBACK (apply_text), buffer); + provider = gtk_css_provider_new (); + gtk_css_provider_load_from_data (provider, "button.small { padding: 0; }", -1); + gtk_style_context_add_provider (gtk_widget_get_style_context (button), + GTK_STYLE_PROVIDER (provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + g_object_unref (provider); + gtk_widget_set_halign (button, GTK_ALIGN_CENTER); + gtk_widget_set_valign (button, GTK_ALIGN_CENTER); + gtk_widget_add_css_class (button, "small"); + gtk_widget_hide (button); + gtk_center_box_set_end_widget (GTK_CENTER_BOX (widget), button); + + gtk_box_append (GTK_BOX (vbox), widget); + + gtk_box_append (GTK_BOX (vbox), stack); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_widget_set_halign (hbox, GTK_ALIGN_CENTER); + + gtk_box_append (GTK_BOX (vbox), hbox); + + button = gtk_button_new_from_icon_name ("go-previous"); + g_signal_connect (button, "clicked", G_CALLBACK (go_back), stack); + bin = fire_bin_new (); + gtk_shader_bin_set_child (GTK_SHADER_BIN (bin), button); + gtk_box_append (GTK_BOX (hbox), bin); + + button = gtk_button_new_from_icon_name ("go-next"); + g_signal_connect (button, "clicked", G_CALLBACK (go_forward), stack); + bin = fire_bin_new (); + gtk_shader_bin_set_child (GTK_SHADER_BIN (bin), button); + gtk_box_append (GTK_BOX (hbox), bin); + + return vbox; +} + +static GtkWidget * +create_gltransition_window (GtkWidget *do_widget) +{ + GtkWidget *window, *headerbar, *scale, *grid; + + window = gtk_window_new (); + gtk_window_set_display (GTK_WINDOW (window), gtk_widget_get_display (do_widget)); + gtk_window_set_title (GTK_WINDOW (window), "Transitions"); + headerbar = gtk_header_bar_new (); + scale = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, NULL); + gtk_scale_set_draw_value (GTK_SCALE (scale), FALSE); + gtk_widget_set_size_request (scale, 100, -1); + gtk_header_bar_pack_end (GTK_HEADER_BAR (headerbar), scale); + gtk_window_set_titlebar (GTK_WINDOW (window), headerbar); + gtk_window_set_default_size (GTK_WINDOW (window), 800, 600); + g_signal_connect (window, "destroy", G_CALLBACK (close_window), NULL); + + grid = gtk_grid_new (); + gtk_window_set_child (GTK_WINDOW (window), grid); + + gtk_widget_set_halign (grid, GTK_ALIGN_CENTER); + gtk_widget_set_valign (grid, GTK_ALIGN_CENTER); + gtk_widget_set_margin_start (grid, 12); + gtk_widget_set_margin_end (grid, 12); + gtk_widget_set_margin_top (grid, 12); + gtk_widget_set_margin_bottom (grid, 12); + gtk_grid_set_row_spacing (GTK_GRID (grid), 6); + gtk_grid_set_column_spacing (GTK_GRID (grid), 6); + gtk_grid_set_row_homogeneous (GTK_GRID (grid), TRUE); + gtk_grid_set_column_homogeneous (GTK_GRID (grid), TRUE); + + gtk_grid_attach (GTK_GRID (grid), + make_shader_stack ("Wind", "/gltransition/transition1.glsl", scale), + 0, 0, 1, 1); + gtk_grid_attach (GTK_GRID (grid), + make_shader_stack ("Radial", "/gltransition/transition2.glsl", scale), + 1, 0, 1, 1); + gtk_grid_attach (GTK_GRID (grid), + make_shader_stack ("Crosswarp", "/gltransition/transition3.glsl", scale), + 0, 1, 1, 1); + gtk_grid_attach (GTK_GRID (grid), + make_shader_stack ("Kaleidoscope", "/gltransition/transition4.glsl", scale), + 1, 1, 1, 1); + + return window; +} + +GtkWidget * +do_gltransition (GtkWidget *do_widget) +{ + if (!demo_window) + demo_window = create_gltransition_window (do_widget); + + if (!gtk_widget_get_visible (demo_window)) + gtk_widget_show (demo_window); + else + gtk_window_destroy (GTK_WINDOW (demo_window)); + + return demo_window; +} diff --git a/demos/gtk-demo/gskshaderpaintable.c b/demos/gtk-demo/gskshaderpaintable.c new file mode 100644 index 0000000000..3eb0cbc89f --- /dev/null +++ b/demos/gtk-demo/gskshaderpaintable.c @@ -0,0 +1,337 @@ +/* + * Copyright © 2020 Red Hat, Inc + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Matthias Clasen + */ + +#include "config.h" + +#include +#include "gskshaderpaintable.h" + +/** + * SECTION:gskshaderpaintable + * @Short_description: Drawing with shaders + * @Title: GskShaderPaintable + * @see_also: #GdkPaintable + * + * GskShaderPaintable is an implementation of the #GdkPaintable interface + * that uses a #GskGLShader to create pixels. + * + * You can set the uniform data that the shader needs for rendering + * using gsk_shader_paintable_set_args(). This function can + * be called repeatedly to change the uniform data for the next + * snapshot. + * + * Commonly, time is passed to shaders as a float uniform containing + * the elapsed time in seconds. The convenience API + * gsk_shader_paintable_update_time() can be called from a #GtkTickCallback + * to update the time based on the frame time of the frame clock. + */ + + +struct _GskShaderPaintable +{ + GObject parent_instance; + + GskGLShader *shader; + GBytes *args; + + gint64 start_time; +}; + +struct _GskShaderPaintableClass +{ + GObjectClass parent_class; +}; + +enum { + PROP_0, + PROP_SHADER, + PROP_ARGS, + + N_PROPS, +}; + +static GParamSpec *properties[N_PROPS] = { NULL, }; + +static void +gsk_shader_paintable_paintable_snapshot (GdkPaintable *paintable, + GdkSnapshot *snapshot, + double width, + double height) +{ + GskShaderPaintable *self = GSK_SHADER_PAINTABLE (paintable); + + gtk_snapshot_push_gl_shader (snapshot, self->shader, &GRAPHENE_RECT_INIT(0, 0, width, height), g_bytes_ref (self->args)); + gtk_snapshot_pop (snapshot); +} + +static void +gsk_shader_paintable_paintable_init (GdkPaintableInterface *iface) +{ + iface->snapshot = gsk_shader_paintable_paintable_snapshot; +} + +G_DEFINE_TYPE_EXTENDED (GskShaderPaintable, gsk_shader_paintable, G_TYPE_OBJECT, 0, + G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE, + gsk_shader_paintable_paintable_init)) + +static void +gsk_shader_paintable_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) + +{ + GskShaderPaintable *self = GSK_SHADER_PAINTABLE (object); + + switch (prop_id) + { + case PROP_SHADER: + gsk_shader_paintable_set_shader (self, g_value_get_object (value)); + break; + + case PROP_ARGS: + gsk_shader_paintable_set_args (self, g_value_get_boxed (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gsk_shader_paintable_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GskShaderPaintable *self = GSK_SHADER_PAINTABLE (object); + + switch (prop_id) + { + case PROP_SHADER: + g_value_set_object (value, self->shader); + break; + + case PROP_ARGS: + g_value_set_boxed (value, self->args); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gsk_shader_paintable_finalize (GObject *object) +{ + GskShaderPaintable *self = GSK_SHADER_PAINTABLE (object); + + g_clear_pointer (&self->args, g_bytes_unref); + g_clear_object (&self->shader); + + G_OBJECT_CLASS (gsk_shader_paintable_parent_class)->finalize (object); +} + +static void +gsk_shader_paintable_class_init (GskShaderPaintableClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->get_property = gsk_shader_paintable_get_property; + gobject_class->set_property = gsk_shader_paintable_set_property; + gobject_class->finalize = gsk_shader_paintable_finalize; + + properties[PROP_SHADER] = + g_param_spec_object ("shader", "Shader", "The shader", + GSK_TYPE_GL_SHADER, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + properties[PROP_ARGS] = + g_param_spec_boxed ("args", "Arguments", "The uniform arguments", + G_TYPE_BYTES, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPS, properties); +} + +static void +gsk_shader_paintable_init (GskShaderPaintable *self) +{ +} + +/** + * gsk_shader_paintable_new: + * @shader: (transfer full) (nullable): the shader to use + * @data: (transfer full) (nullable): uniform data + * + * Creates a paintable that uses the @shader to create + * pixels. The shader must not require input textures. + * If @data is %NULL, all uniform values are set to zero. + * + * Returns: (transfer full): a new #GskShaderPaintable + */ +GdkPaintable * +gsk_shader_paintable_new (GskGLShader *shader, + GBytes *data) +{ + GdkPaintable *ret; + + g_return_val_if_fail (shader == NULL || GSK_IS_GL_SHADER (shader), NULL); + + if (shader && !data) + { + int size = gsk_gl_shader_get_args_size (shader); + data = g_bytes_new_take (g_new0 (guchar, size), size); + } + + ret = g_object_new (GSK_TYPE_SHADER_PAINTABLE, + "shader", shader, + "args", data, + NULL); + + g_clear_object (&shader); + g_clear_pointer (&data, g_bytes_unref); + + return ret; +} + +/** + * gsk_shader_paintable_set_shader: + * @self: a #GskShaderPaintable + * @shader: the #GskGLShader to use + * + * Sets the shader that the paintable will use + * to create pixels. The shader must not require + * input textures. + */ +void +gsk_shader_paintable_set_shader (GskShaderPaintable *self, + GskGLShader *shader) +{ + g_return_if_fail (GSK_IS_SHADER_PAINTABLE (self)); + g_return_if_fail (shader == NULL || GSK_IS_GL_SHADER (shader)); + g_return_if_fail (shader == NULL || gsk_gl_shader_get_n_textures (shader) == 0); + + if (!g_set_object (&self->shader, shader)) + return; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHADER]); + gdk_paintable_invalidate_contents (GDK_PAINTABLE (self)); + + g_clear_pointer (&self->args, g_bytes_unref); +} + +/** + * gsk_shader_paintable_get_shader: + * @self: a #GskShaderPaintable + * + * Returns the shader that the paintable is using. + * + * Returns: (transfer none): the #GskGLShader that is used + */ +GskGLShader * +gsk_shader_paintable_get_shader (GskShaderPaintable *self) +{ + g_return_val_if_fail (GSK_IS_SHADER_PAINTABLE (self), NULL); + + return self->shader; +} + +/** + * gsk_shader_paintable_set_args: + * @self: a #GskShaderPaintable + * @data: Data block with uniform data for the shader + * + * Sets the uniform data that will be passed to the + * shader when rendering. The @data will typically + * be produced by a #GskUniformDataBuilder. + * + * Note that the @data should be considered immutable + * after it has been passed to this function. + */ +void +gsk_shader_paintable_set_args (GskShaderPaintable *self, + GBytes *data) +{ + g_return_if_fail (GSK_IS_SHADER_PAINTABLE (self)); + g_return_if_fail (data == NULL || g_bytes_get_size (data) == gsk_gl_shader_get_args_size (self->shader)); + + g_clear_pointer (&self->args, g_bytes_unref); + if (data) + self->args = g_bytes_ref (data); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ARGS]); + gdk_paintable_invalidate_contents (GDK_PAINTABLE (self)); +} + +/** + * gsk_shader_paintable_get_args: + * @self: a #GskShaderPaintable + * + * Returns the uniform data set with + * gsk_shader_paintable_get_args(). + * + * Returns: (transfer none): the uniform data + */ +GBytes * +gsk_shader_paintable_get_args (GskShaderPaintable *self) +{ + g_return_val_if_fail (GSK_IS_SHADER_PAINTABLE (self), NULL); + + return self->args; +} + +/** + * gsk_shader_paintable_update_time: + * @self: a #GskShaderPaintable + * @time_idx: the index of the uniform for time in seconds as float + * @frame_time: the current frame time, as returned by #GdkFrameClock + * + * This function is a convenience wrapper for + * gsk_shader_paintable_set_args() that leaves all + * uniform values unchanged, except for the uniform with + * index @time_idx, which will be set to the elapsed time + * in seconds, since the first call to this function. + * + * This function is usually called from a #GtkTickCallback. + */ +void +gsk_shader_paintable_update_time (GskShaderPaintable *self, + int time_idx, + gint64 frame_time) +{ + GskShaderArgsBuilder *builder; + GBytes *args; + float time; + + if (self->start_time == 0) + self->start_time = frame_time; + + time = (frame_time - self->start_time) / (float)G_TIME_SPAN_SECOND; + + builder = gsk_shader_args_builder_new (self->shader, self->args); + gsk_shader_args_builder_set_float (builder, time_idx, time); + args = gsk_shader_args_builder_free_to_args (builder); + + gsk_shader_paintable_set_args (self, args); + + g_bytes_unref (args); +} diff --git a/demos/gtk-demo/gskshaderpaintable.h b/demos/gtk-demo/gskshaderpaintable.h new file mode 100644 index 0000000000..9b8f7db83f --- /dev/null +++ b/demos/gtk-demo/gskshaderpaintable.h @@ -0,0 +1,53 @@ +/* + * Copyright © 2020 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Matthias Clasen + */ + +#ifndef __GSK_SHADER_PAINTABLE_H__ +#define __GSK_SHADER_PAINTABLE_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GSK_TYPE_SHADER_PAINTABLE (gsk_shader_paintable_get_type ()) + +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GskShaderPaintable, gsk_shader_paintable, GSK, SHADER_PAINTABLE, GObject) + +GDK_AVAILABLE_IN_ALL +GdkPaintable * gsk_shader_paintable_new (GskGLShader *shader, + GBytes *data); + +GDK_AVAILABLE_IN_ALL +GskGLShader * gsk_shader_paintable_get_shader (GskShaderPaintable *self); +GDK_AVAILABLE_IN_ALL +void gsk_shader_paintable_set_shader (GskShaderPaintable *self, + GskGLShader *shader); +GDK_AVAILABLE_IN_ALL +GBytes * gsk_shader_paintable_get_args (GskShaderPaintable *self); +GDK_AVAILABLE_IN_ALL +void gsk_shader_paintable_set_args (GskShaderPaintable *self, + GBytes *data); +GDK_AVAILABLE_IN_ALL +void gsk_shader_paintable_update_time (GskShaderPaintable *self, + int time_idx, + gint64 frame_time); +G_END_DECLS + +#endif /* __GSK_SHADER_PAINTABLE_H__ */ diff --git a/demos/gtk-demo/gtkshaderbin.c b/demos/gtk-demo/gtkshaderbin.c new file mode 100644 index 0000000000..3a97311c3e --- /dev/null +++ b/demos/gtk-demo/gtkshaderbin.c @@ -0,0 +1,241 @@ +#include "gtkshaderbin.h" + +typedef struct { + GskGLShader *shader; + GtkStateFlags state; + GtkStateFlags state_mask; + gboolean compiled; + gboolean compiled_ok; +} ShaderInfo; + +struct _GtkShaderBin +{ + GtkWidget parent_instance; + GtkWidget *child; + ShaderInfo *active_shader; + GPtrArray *shaders; + guint tick_id; + float time; + gint64 first_frame_time; +}; + +struct _GtkShaderBinClass +{ + GtkWidgetClass parent_class; +}; + +G_DEFINE_TYPE (GtkShaderBin, gtk_shader_bin, GTK_TYPE_WIDGET) + +static void +shader_info_free (ShaderInfo *info) +{ + g_object_unref (info->shader); + g_free (info); +} + +static void +gtk_shader_bin_finalize (GObject *object) +{ + GtkShaderBin *self = GTK_SHADER_BIN (object); + + g_ptr_array_free (self->shaders, TRUE); + + G_OBJECT_CLASS (gtk_shader_bin_parent_class)->finalize (object); +} + +static void +gtk_shader_bin_dispose (GObject *object) +{ + GtkShaderBin *self = GTK_SHADER_BIN (object); + + g_clear_pointer (&self->child, gtk_widget_unparent); + + G_OBJECT_CLASS (gtk_shader_bin_parent_class)->dispose (object); +} + +static gboolean +gtk_shader_bin_tick (GtkWidget *widget, + GdkFrameClock *frame_clock, + gpointer unused) +{ + GtkShaderBin *self = GTK_SHADER_BIN (widget); + gint64 frame_time; + + frame_time = gdk_frame_clock_get_frame_time (frame_clock); + if (self->first_frame_time == 0) + self->first_frame_time = frame_time; + self->time = (frame_time - self->first_frame_time) / (float)G_USEC_PER_SEC; + + gtk_widget_queue_draw (widget); + + return G_SOURCE_CONTINUE; +} + +static void +gtk_shader_bin_init (GtkShaderBin *self) +{ + self->shaders = g_ptr_array_new_with_free_func ((GDestroyNotify)shader_info_free); +} + +void +gtk_shader_bin_update_active_shader (GtkShaderBin *self) +{ + GtkStateFlags new_state = gtk_widget_get_state_flags (GTK_WIDGET (self)); + ShaderInfo *new_shader = NULL; + + for (int i = 0; i < self->shaders->len; i++) + { + ShaderInfo *info = g_ptr_array_index (self->shaders, i); + + if ((info->state_mask & new_state) == info->state) + { + new_shader = info; + break; + } + } + + if (self->active_shader == new_shader) + return; + + self->active_shader = new_shader; + self->first_frame_time = 0; + + if (self->active_shader) + { + if (self->tick_id == 0) + self->tick_id = gtk_widget_add_tick_callback (GTK_WIDGET (self), + gtk_shader_bin_tick, + NULL, NULL); + } + else + { + if (self->tick_id != 0) + { + gtk_widget_remove_tick_callback (GTK_WIDGET (self), self->tick_id); + self->tick_id = 0; + } + } + + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +static void +gtk_shader_bin_state_flags_changed (GtkWidget *widget, + GtkStateFlags previous_state_flags) +{ + GtkShaderBin *self = GTK_SHADER_BIN (widget); + + gtk_shader_bin_update_active_shader (self); +} + +void +gtk_shader_bin_add_shader (GtkShaderBin *self, + GskGLShader *shader, + GtkStateFlags state, + GtkStateFlags state_mask) +{ + ShaderInfo *info = g_new0 (ShaderInfo, 1); + info->shader = g_object_ref (shader); + info->state = state; + info->state_mask = state_mask; + + g_ptr_array_add (self->shaders, info); + + gtk_shader_bin_update_active_shader (self); +} + +void +gtk_shader_bin_set_child (GtkShaderBin *self, + GtkWidget *child) +{ + + if (self->child == child) + return; + + g_clear_pointer (&self->child, gtk_widget_unparent); + + if (child) + { + self->child = child; + gtk_widget_set_parent (child, GTK_WIDGET (self)); + } +} + +GtkWidget * +gtk_shader_bin_get_child (GtkShaderBin *self) +{ + return self->child; +} + +static void +gtk_shader_bin_snapshot (GtkWidget *widget, + GtkSnapshot *snapshot) +{ + GtkShaderBin *self = GTK_SHADER_BIN (widget); + int width, height; + + width = gtk_widget_get_width (widget); + height = gtk_widget_get_height (widget); + + if (self->active_shader) + { + if (!self->active_shader->compiled) + { + GtkNative *native = gtk_widget_get_native (widget); + GskRenderer *renderer = gtk_native_get_renderer (native); + GError *error = NULL; + + self->active_shader->compiled = TRUE; + self->active_shader->compiled_ok = + gsk_gl_shader_compile (self->active_shader->shader, + renderer, &error); + if (!self->active_shader->compiled_ok) + { + g_warning ("GtkShaderBin failed to compile shader: %s", error->message); + g_error_free (error); + } + } + + if (self->active_shader->compiled_ok) + { + gtk_snapshot_push_gl_shader (snapshot, self->active_shader->shader, + &GRAPHENE_RECT_INIT(0, 0, width, height), + gsk_gl_shader_format_args (self->active_shader->shader, + "u_time", self->time, + NULL)); + gtk_widget_snapshot_child (widget, self->child, snapshot); + gtk_snapshot_gl_shader_pop_texture (snapshot); + gtk_snapshot_pop (snapshot); + + return; + } + } + + /* Non-shader fallback */ + gtk_widget_snapshot_child (widget, self->child, snapshot); +} + +static void +gtk_shader_bin_class_init (GtkShaderBinClass *class) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->finalize = gtk_shader_bin_finalize; + object_class->dispose = gtk_shader_bin_dispose; + + gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); + + widget_class->snapshot = gtk_shader_bin_snapshot; + widget_class->state_flags_changed = gtk_shader_bin_state_flags_changed; +} + +GtkWidget * +gtk_shader_bin_new (void) +{ + GtkShaderBin *self; + + self = g_object_new (GTK_TYPE_SHADER_BIN, NULL); + + return GTK_WIDGET (self); +} diff --git a/demos/gtk-demo/gtkshaderbin.h b/demos/gtk-demo/gtkshaderbin.h new file mode 100644 index 0000000000..eabac8f0d6 --- /dev/null +++ b/demos/gtk-demo/gtkshaderbin.h @@ -0,0 +1,22 @@ +#ifndef __GTK_SHADER_BIN_H__ +#define __GTK_SHADER_BIN_H__ + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_SHADER_BIN (gtk_shader_bin_get_type ()) +G_DECLARE_FINAL_TYPE (GtkShaderBin, gtk_shader_bin, GTK, SHADER_BIN, GtkWidget) + +GtkWidget *gtk_shader_bin_new (void); +void gtk_shader_bin_add_shader (GtkShaderBin *self, + GskGLShader *shader, + GtkStateFlags state, + GtkStateFlags state_mask); +void gtk_shader_bin_set_child (GtkShaderBin *self, + GtkWidget *child); +GtkWidget *gtk_shader_bin_get_child (GtkShaderBin *self); + +G_END_DECLS + +#endif /* __GTK_SHADER_BIN_H__ */ diff --git a/demos/gtk-demo/gtkshaderstack.c b/demos/gtk-demo/gtkshaderstack.c new file mode 100644 index 0000000000..27194df3b1 --- /dev/null +++ b/demos/gtk-demo/gtkshaderstack.c @@ -0,0 +1,352 @@ +#include "gtkshaderstack.h" + +struct _GtkShaderStack +{ + GtkWidget parent_instance; + + GskGLShader *shader; + GPtrArray *children; + int current; + int next; + gboolean backwards; + + guint tick_id; + float time; + float duration; + gint64 start_time; +}; + +struct _GtkShaderStackClass +{ + GtkWidgetClass parent_class; +}; + + +enum { + PROP_DURATION = 1, + NUM_PROPERTIES +}; + +static GParamSpec *properties[NUM_PROPERTIES] = { NULL }; + +G_DEFINE_TYPE (GtkShaderStack, gtk_shader_stack, GTK_TYPE_WIDGET) + +static void +gtk_shader_stack_finalize (GObject *object) +{ + GtkShaderStack *self = GTK_SHADER_STACK (object); + + g_object_unref (self->shader); + + G_OBJECT_CLASS (gtk_shader_stack_parent_class)->finalize (object); +} + +static void +update_child_visible (GtkShaderStack *self) +{ + int i; + + for (i = 0; i < self->children->len; i++) + { + GtkWidget *child = g_ptr_array_index (self->children, i); + + gtk_widget_set_child_visible (child, + i == self->current || i == self->next); + } +} + +static gboolean +transition_cb (GtkWidget *widget, + GdkFrameClock *clock, + gpointer unused) +{ + GtkShaderStack *self = GTK_SHADER_STACK (widget); + gint64 frame_time; + + frame_time = gdk_frame_clock_get_frame_time (clock); + + if (self->start_time == 0) + self->start_time = frame_time; + + self->time = (frame_time - self->start_time) / (float)G_USEC_PER_SEC; + + gtk_widget_queue_draw (widget); + + if (self->time >= self->duration) + { + self->current = self->next; + self->next = -1; + + update_child_visible (self); + + return G_SOURCE_REMOVE; + } + else + return G_SOURCE_CONTINUE; +} + +static void +start_transition (GtkShaderStack *self) +{ + self->start_time = 0; + self->tick_id = gtk_widget_add_tick_callback (GTK_WIDGET (self), + transition_cb, + NULL, NULL); +} + +static void +stop_transition (GtkShaderStack *self) +{ + if (self->tick_id != 0) + { + gtk_widget_remove_tick_callback (GTK_WIDGET (self), self->tick_id); + self->tick_id = 0; + } + + if (self->next != -1) + self->current = self->next; + self->next = -1; + + update_child_visible (self); +} + +static void +gtk_shader_stack_dispose (GObject *object) +{ + GtkShaderStack *self = GTK_SHADER_STACK (object); + + stop_transition (self); + + g_clear_pointer (&self->children, g_ptr_array_unref); + + G_OBJECT_CLASS (gtk_shader_stack_parent_class)->dispose (object); +} + +void +gtk_shader_stack_transition (GtkShaderStack *self, + gboolean forward) +{ + stop_transition (self); + + self->backwards = !forward; + if (self->backwards) + self->next = (self->current + self->children->len - 1) % self->children->len; + else + self->next = (self->current + 1) % self->children->len; + + update_child_visible (self); + + start_transition (self); +} + +static void +gtk_shader_stack_init (GtkShaderStack *self) +{ + self->children = g_ptr_array_new_with_free_func ((GDestroyNotify)gtk_widget_unparent); + self->current = -1; + self->next = -1; + self->backwards = FALSE; + self->duration = 1.0; +} + +static void +gtk_shader_stack_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + GtkShaderStack *self = GTK_SHADER_STACK (widget); + int i; + + *minimum = 0; + *natural = 0; + + for (i = 0; i < self->children->len; i++) + { + GtkWidget *child = g_ptr_array_index (self->children, i); + int child_min, child_nat; + + if (gtk_widget_get_visible (child)) + { + gtk_widget_measure (child, orientation, for_size, &child_min, &child_nat, NULL, NULL); + + *minimum = MAX (*minimum, child_min); + *natural = MAX (*natural, child_nat); + } + } +} + +static void +gtk_shader_stack_size_allocate (GtkWidget *widget, + int width, + int height, + int baseline) +{ + GtkShaderStack *self = GTK_SHADER_STACK (widget); + GtkAllocation child_allocation; + GtkWidget *child; + int i; + + child_allocation.x = 0; + child_allocation.y = 0; + child_allocation.width = width; + child_allocation.height = height; + + for (i = 0; i < self->children->len; i++) + { + child = g_ptr_array_index (self->children, i); + if (gtk_widget_get_visible (child)) + gtk_widget_size_allocate (child, &child_allocation, -1); + } +} + +static void +gtk_shader_stack_snapshot (GtkWidget *widget, + GtkSnapshot *snapshot) +{ + GtkShaderStack *self = GTK_SHADER_STACK (widget); + int width, height; + GtkWidget *current, *next; + + width = gtk_widget_get_width (widget); + height = gtk_widget_get_height (widget); + + current = g_ptr_array_index (self->children, self->current); + + if (self->next == -1) + { + gtk_widget_snapshot_child (widget, current, snapshot); + } + else + { + GtkNative *native = gtk_widget_get_native (widget); + GskRenderer *renderer = gtk_native_get_renderer (native); + float progress; + + next = g_ptr_array_index (self->children, self->next); + + progress = self->time / self->duration; + + if (self->backwards) + { + GtkWidget *tmp = next; + next = current; + current = tmp; + progress = 1. - progress; + } + + if (gsk_gl_shader_compile (self->shader, renderer, NULL)) + { + gtk_snapshot_push_gl_shader (snapshot, + self->shader, + &GRAPHENE_RECT_INIT(0, 0, width, height), + gsk_gl_shader_format_args (self->shader, + "progress", progress, + NULL)); + + gtk_widget_snapshot_child (widget, current, snapshot); + gtk_snapshot_gl_shader_pop_texture (snapshot); /* current child */ + gtk_widget_snapshot_child (widget, next, snapshot); + gtk_snapshot_gl_shader_pop_texture (snapshot); /* next child */ + gtk_snapshot_pop (snapshot); + } + else + { + /* Non-shader fallback */ + gtk_widget_snapshot_child (widget, current, snapshot); + } + } +} + +static void +gtk_shader_stack_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkShaderStack *self = GTK_SHADER_STACK (object); + + switch (prop_id) + { + case PROP_DURATION: + g_value_set_float (value, self->duration); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_shader_stack_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkShaderStack *self = GTK_SHADER_STACK (object); + + switch (prop_id) + { + case PROP_DURATION: + self->duration = g_value_get_float (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_shader_stack_class_init (GtkShaderStackClass *class) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->finalize = gtk_shader_stack_finalize; + object_class->dispose = gtk_shader_stack_dispose; + object_class->get_property = gtk_shader_stack_get_property; + object_class->set_property = gtk_shader_stack_set_property; + + widget_class->snapshot = gtk_shader_stack_snapshot; + widget_class->measure = gtk_shader_stack_measure; + widget_class->size_allocate = gtk_shader_stack_size_allocate; + + properties[PROP_DURATION] = + g_param_spec_float ("duration", "Duration", "Duration", + 0.1, 3.0, 1.0, + G_PARAM_READWRITE); + + g_object_class_install_properties (object_class, NUM_PROPERTIES, properties); +} + +GtkWidget * +gtk_shader_stack_new (void) +{ + return g_object_new (GTK_TYPE_SHADER_STACK, NULL); +} + +void +gtk_shader_stack_set_shader (GtkShaderStack *self, + GskGLShader *shader) +{ + g_set_object (&self->shader, shader); +} + +void +gtk_shader_stack_add_child (GtkShaderStack *self, + GtkWidget *child) +{ + g_ptr_array_add (self->children, child); + gtk_widget_set_parent (child, GTK_WIDGET (self)); + gtk_widget_queue_resize (GTK_WIDGET (self)); + + if (self->current == -1) + self->current = 0; + else + gtk_widget_set_child_visible (child, FALSE); +} diff --git a/demos/gtk-demo/gtkshaderstack.h b/demos/gtk-demo/gtkshaderstack.h new file mode 100644 index 0000000000..9f8e4e45e5 --- /dev/null +++ b/demos/gtk-demo/gtkshaderstack.h @@ -0,0 +1,21 @@ +#ifndef __GTK_SHADER_STACK_H__ +#define __GTK_SHADER_STACK_H__ + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_SHADER_STACK (gtk_shader_stack_get_type ()) +G_DECLARE_FINAL_TYPE (GtkShaderStack, gtk_shader_stack, GTK, SHADER_STACK, GtkWidget) + +GtkWidget * gtk_shader_stack_new (void); +void gtk_shader_stack_set_shader (GtkShaderStack *self, + GskGLShader *shader); +void gtk_shader_stack_add_child (GtkShaderStack *self, + GtkWidget *child); +void gtk_shader_stack_transition (GtkShaderStack *self, + gboolean forward); + +G_END_DECLS + +#endif /* __GTK_SHADER_STACK_H__ */ diff --git a/demos/gtk-demo/meson.build b/demos/gtk-demo/meson.build index a32fee88e5..ecb08ebaac 100644 --- a/demos/gtk-demo/meson.build +++ b/demos/gtk-demo/meson.build @@ -32,6 +32,7 @@ demos = files([ 'gears.c', 'gestures.c', 'glarea.c', + 'gltransition.c', 'headerbar.c', 'hypertext.c', 'iconscroll.c', @@ -102,7 +103,10 @@ extra_demo_sources = files(['main.c', 'gtkfishbowl.c', 'fontplane.c', 'gtkgears.c', + 'gtkshaderbin.c', 'gtkshadertoy.c', + 'gtkshaderstack.c', + 'gskshaderpaintable.c', 'puzzlepiece.c', 'bluroverlay.c', 'demoimage.c', diff --git a/demos/gtk-demo/transition1.glsl b/demos/gtk-demo/transition1.glsl new file mode 100644 index 0000000000..169bdcbd06 --- /dev/null +++ b/demos/gtk-demo/transition1.glsl @@ -0,0 +1,33 @@ +uniform float progress; +uniform sampler2D u_texture1; +uniform sampler2D u_texture2; + +vec4 getFromColor (vec2 uv) { + return GskTexture(u_texture1, uv); +} + +vec4 getToColor (vec2 uv) { + return GskTexture(u_texture2, uv); +} + +// Source: https://gl-transitions.com/editor/wind +// Author: gre +// License: MIT + +const float size = 0.2; + +float rand(vec2 co) { + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +vec4 transition(vec2 p) { + float r = rand(vec2(0, p.y)); + float m = smoothstep(0.0, -size, p.x*(1.0-size) + size*r - (progress * (1.0 + size))); + return mix(getFromColor(p), getToColor(p), m); +} + + +void mainImage(out vec4 fragColor, in vec2 fragCoord, in vec2 resolution, in vec2 uv) +{ + fragColor = transition(uv); +} diff --git a/demos/gtk-demo/transition2.glsl b/demos/gtk-demo/transition2.glsl new file mode 100644 index 0000000000..11757f4e60 --- /dev/null +++ b/demos/gtk-demo/transition2.glsl @@ -0,0 +1,34 @@ +uniform float progress; +uniform sampler2D u_texture1; +uniform sampler2D u_texture2; + +vec4 getFromColor (vec2 uv) { + return GskTexture(u_texture1, uv); +} + +vec4 getToColor (vec2 uv) { + return GskTexture(u_texture2, uv); +} + +// Source: https://gl-transitions.com/editor/Radial +// License: MIT +// Author: Xaychru + +const float smoothness = 1.0; + +const float PI = 3.141592653589; + +vec4 transition(vec2 p) { + vec2 rp = p*2.-1.; + return mix( + getToColor(p), + getFromColor(p), + smoothstep(0., smoothness, atan(rp.y,rp.x) - (progress-.5) * PI * 2.5) + ); +} + + +void mainImage(out vec4 fragColor, in vec2 fragCoord, in vec2 resolution, in vec2 uv) +{ + fragColor = transition(uv); +} diff --git a/demos/gtk-demo/transition3.glsl b/demos/gtk-demo/transition3.glsl new file mode 100644 index 0000000000..e1cc5f9afc --- /dev/null +++ b/demos/gtk-demo/transition3.glsl @@ -0,0 +1,27 @@ +uniform float progress; +uniform sampler2D u_texture1; +uniform sampler2D u_texture2; + +vec4 getFromColor (vec2 uv) { + return GskTexture(u_texture1, uv); +} + +vec4 getToColor (vec2 uv) { + return GskTexture(u_texture2, uv); +} + +// Source: https://gl-transitions.com/editor/crosswarp +// Author: Eke Péter +// License: MIT + +vec4 transition(vec2 p) { + float x = progress; + x=smoothstep(.0,1.0,(x*2.0+p.x-1.0)); + return mix(getFromColor((p-.5)*(1.-x)+.5), getToColor((p-.5)*x+.5), x); +} + + +void mainImage(out vec4 fragColor, in vec2 fragCoord, in vec2 resolution, in vec2 uv) +{ + fragColor = transition(uv); +} diff --git a/demos/gtk-demo/transition4.glsl b/demos/gtk-demo/transition4.glsl new file mode 100644 index 0000000000..d516b3de59 --- /dev/null +++ b/demos/gtk-demo/transition4.glsl @@ -0,0 +1,41 @@ +uniform float progress; +uniform sampler2D u_texture1; +uniform sampler2D u_texture2; + +vec4 getFromColor (vec2 uv) { + return GskTexture(u_texture1, uv); +} + +vec4 getToColor (vec2 uv) { + return GskTexture(u_texture2, uv); +} + +// Source: https://gl-transitions.com/editor/kaleidoscope +// Author: nwoeanhinnogaehr +// License: MIT + +const float speed = 1.0; +const float angle = 1.0; +const float power = 1.5; + +vec4 transition(vec2 uv) { + vec2 p = uv.xy / vec2(1.0).xy; + vec2 q = p; + float t = pow(progress, power)*speed; + p = p -0.5; + for (int i = 0; i < 7; i++) { + p = vec2(sin(t)*p.x + cos(t)*p.y, sin(t)*p.y - cos(t)*p.x); + t += angle; + p = abs(mod(p, 2.0) - 1.0); + } + abs(mod(p, 1.0)); + return mix( + mix(getFromColor(q), getToColor(q), progress), + mix(getFromColor(p), getToColor(p), progress), 1.0 - 2.0*abs(progress - 0.5)); +} + + +void mainImage(out vec4 fragColor, in vec2 fragCoord, in vec2 resolution, in vec2 uv) +{ + fragColor = transition(uv); +} -- 2.30.2